Pandas 处理文本字符串 您所在的位置:网站首页 pandas contains 正则 Pandas 处理文本字符串

Pandas 处理文本字符串

2023-08-22 17:46| 来源: 网络整理| 查看: 265

# Pandas 处理文本字符串

序列和索引包含一些列的字符操作方法,这可以使我们轻易操作数组中的各个元素。最重要的是,这些方法可以自动跳过 缺失/NA 值。这些方法可以在str属性中访问到,并且基本上和python内建的(标量)字符串方法同名:

In [1]: s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat']) In [2]: s.str.lower() Out[2]: 0 a 1 b 2 c 3 aaba 4 baca 5 NaN 6 caba 7 dog 8 cat dtype: object In [3]: s.str.upper() Out[3]: 0 A 1 B 2 C 3 AABA 4 BACA 5 NaN 6 CABA 7 DOG 8 CAT dtype: object In [4]: s.str.len() Out[4]: 0 1.0 1 1.0 2 1.0 3 4.0 4 4.0 5 NaN 6 4.0 7 3.0 8 3.0 dtype: float64 In [5]: idx = pd.Index([' jack', 'jill ', ' jesse ', 'frank']) In [6]: idx.str.strip() Out[6]: Index(['jack', 'jill', 'jesse', 'frank'], dtype='object') In [7]: idx.str.lstrip() Out[7]: Index(['jack', 'jill ', 'jesse ', 'frank'], dtype='object') In [8]: idx.str.rstrip() Out[8]: Index([' jack', 'jill', ' jesse', 'frank'], dtype='object')

索引的字符串方法在清理或者转换数据表列的时候非常有用。例如,你的列中或许会包含首位的白空格:

In [9]: df = pd.DataFrame(np.random.randn(3, 2), ...: columns=[' Column A ', ' Column B '], index=range(3)) ...: In [10]: df Out[10]: Column A Column B 0 0.469112 -0.282863 1 -1.509059 -1.135632 2 1.212112 -0.173215

Since df.columns is an Index object, we can use the .str accessor

In [11]: df.columns.str.strip() Out[11]: Index(['Column A', 'Column B'], dtype='object') In [12]: df.columns.str.lower() Out[12]: Index([' column a ', ' column b '], dtype='object')

这些字符串方法可以被用来清理需要的列。这里,我们想清理开头和结尾的白空格,将所有的名称都换为小写,并且将其余的空格都替换为下划线:

In [13]: df.columns = df.columns.str.strip().str.lower().str.replace(' ', '_') In [14]: df Out[14]: column_a column_b 0 0.469112 -0.282863 1 -1.509059 -1.135632 2 1.212112 -0.173215

小贴士

如果你有一个序列,里面有很多重复的值 (即,序列中唯一元素的数量远小于序列的长度),将原有序列转换为一种分类类型,然后使用.str. 或者 .dt.方法,则会获得更快的速度。 速度的差异来源于,在分类类型的序列中,字符操作只是在categories中完成的,而不是针对序列中的每一个元素。

请注意,相比于字符串类型的序列,带.categories类型的 分类 类别的 序列有一些限制(例如,你不能像其中的元素追加其他的字串:s + " " + s 将不能正确工作,如果s是一个分类类型的序列。并且,.str 中,那些可以对 列表(list) 类型的元素进行操作的方法,在分类序列中也无法使用。

警告

v.0.25.0版以前, .str访问器只会进行最基本的类型检查。 从v.0.25.0起,序列的类型会被自动推断出来,并且会更为激进地使用恰当的类型。

一般来说 .str 访问器只倾向于针对字符串类型工作。只有在个别的情况下,才能对非字符串类型工作,但是这也将会在未来的版本中被逐步修正

# 拆分和替换字符串

类似split的方法返回一个列表类型的序列:

In [15]: s2 = pd.Series(['a_b_c', 'c_d_e', np.nan, 'f_g_h']) In [16]: s2.str.split('_') Out[16]: 0 [a, b, c] 1 [c, d, e] 2 NaN 3 [f, g, h] dtype: object

切分后的列表中的元素可以通过 get 方法或者 [] 方法进行读取:

In [17]: s2.str.split('_').str.get(1) Out[17]: 0 b 1 d 2 NaN 3 g dtype: object In [18]: s2.str.split('_').str[1] Out[18]: 0 b 1 d 2 NaN 3 g dtype: object

使用expand方法可以轻易地将这种返回展开为一个数据表.

In [19]: s2.str.split('_', expand=True) Out[19]: 0 1 2 0 a b c 1 c d e 2 NaN NaN NaN 3 f g h

同样,我们也可以限制切分的次数:

In [20]: s2.str.split('_', expand=True, n=1) Out[20]: 0 1 0 a b_c 1 c d_e 2 NaN NaN 3 f g_h

rsplit与split相似,不同的是,这个切分的方向是反的。即,从字串的尾端向首段切分:

In [21]: s2.str.rsplit('_', expand=True, n=1) Out[21]: 0 1 0 a_b c 1 c_d e 2 NaN NaN 3 f_g h

replace 方法默认使用 正则表达式open in new window:

In [22]: s3 = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', ....: '', np.nan, 'CABA', 'dog', 'cat']) ....: In [23]: s3 Out[23]: 0 A 1 B 2 C 3 Aaba 4 Baca 5 6 NaN 7 CABA 8 dog 9 cat dtype: object In [24]: s3.str.replace('^.a|dog', 'XX-XX ', case=False) Out[24]: 0 A 1 B 2 C 3 XX-XX ba 4 XX-XX ca 5 6 NaN 7 XX-XX BA 8 XX-XX 9 XX-XX t dtype: object

一定要时时记得,是正则表达式,因此要格外小心。例如,因为正则表达式中的*$*符号,下列代码将会导致一些麻烦:

# Consider the following badly formatted financial data In [25]: dollars = pd.Series(['12', '-$10', '$10,000']) # This does what you'd naively expect: In [26]: dollars.str.replace('$', '') Out[26]: 0 12 1 -10 2 10,000 dtype: object # But this doesn't: In [27]: dollars.str.replace('-$', '-') Out[27]: 0 12 1 -$10 2 $10,000 dtype: object # We need to escape the special character (for >1 len patterns) In [28]: dollars.str.replace(r'-\$', '-') Out[28]: 0 12 1 -10 2 $10,000 dtype: object

v0.23.0. 新加入

如果你只是向单纯地替换字符 (等价于python中的 str.replace()open in new window),你可以将可选参数 regex 设置为 False,而不是傻傻地转义所有符号。这种情况下,pat 和 repl 就都将作为普通字符对待:

# These lines are equivalent In [29]: dollars.str.replace(r'-\$', '-') Out[29]: 0 12 1 -10 2 $10,000 dtype: object In [30]: dollars.str.replace('-$', '-', regex=False) Out[30]: 0 12 1 -10 2 $10,000 dtype: object

v0.20.0. 新加入

replace 方法也可以传入一个可调用对象作为替换值。它针对每一个 pat 通过re.sub()open in new window来调用。可调用对象应只具有一个形参(一个正则表达式对象)并且返回一个字符串。

# Reverse every lowercase alphabetic word In [31]: pat = r'[a-z]+' In [32]: def repl(m): ....: return m.group(0)[::-1] ....: In [33]: pd.Series(['foo 123', 'bar baz', np.nan]).str.replace(pat, repl) Out[33]: 0 oof 123 1 rab zab 2 NaN dtype: object # Using regex groups In [34]: pat = r"(?P\w+) (?P\w+) (?P\w+)" In [35]: def repl(m): ....: return m.group('two').swapcase() ....: In [36]: pd.Series(['Foo Bar Baz', np.nan]).str.replace(pat, repl) Out[36]: 0 bAR 1 NaN dtype: object

v0.20.0. 新加入

replace 方法也可以接受一个来自re.compile()open in new window 编译过的正则表达式对象,来做为表达式。所有的标记都应该被包含在这个已经编译好的正则表达式对象中。

In [37]: import re In [38]: regex_pat = re.compile(r'^.a|dog', flags=re.IGNORECASE) In [39]: s3.str.replace(regex_pat, 'XX-XX ') Out[39]: 0 A 1 B 2 C 3 XX-XX ba 4 XX-XX ca 5 6 NaN 7 XX-XX BA 8 XX-XX 9 XX-XX t dtype: object

如果在已经使用编译的正则对象中继续传入flags 参数,并进行替换,将会导致ValueError。

In [40]: s3.str.replace(regex_pat, 'XX-XX ', flags=re.IGNORECASE) --------------------------------------------------------------------------- ValueError: case and flags cannot be set when pat is a compiled regex # 拼接

Pandas提供了不同的方法将序列或索引与他们自己或者其他的对象进行拼接,所有的方法都是基于各自的cat()open in new window, resp. Index.str.cat.

# 将单个序列拼接为一个完整字符串

序列或索引的内容可以进行拼接:

In [41]: s = pd.Series(['a', 'b', 'c', 'd']) In [42]: s.str.cat(sep=',') Out[42]: 'a,b,c,d'

如果没有额外声明,sep 即分隔符默认为空字串,即sep='':

In [43]: s.str.cat() Out[43]: 'abcd'

默认情况下,缺失值会被忽略。使用na_rep参数,可以对缺失值进行赋值:

In [44]: t = pd.Series(['a', 'b', np.nan, 'd']) In [45]: t.str.cat(sep=',') Out[45]: 'a,b,d' In [46]: t.str.cat(sep=',', na_rep='-') Out[46]: 'a,b,-,d' # 拼接序列和其他类列表型对象为新的序列

cat()open in new window 的第一个参数为类列表对象,但必须要确保长度与序列或索引相同.

In [47]: s.str.cat(['A', 'B', 'C', 'D']) Out[47]: 0 aA 1 bB 2 cC 3 dD dtype: object

任何一端的缺失值都会导致之中结果为缺失值,除非使用na_rep:

In [48]: s.str.cat(t) Out[48]: 0 aa 1 bb 2 NaN 3 dd dtype: object In [49]: s.str.cat(t, na_rep='-') Out[49]: 0 aa 1 bb 2 c- 3 dd dtype: object # 拼接序列与类数组对象为新的序列

v0.23.0. 新加入

others 参数可以是二维的。此时,行数需要与序列或索引的长度相同。

In [50]: d = pd.concat([t, s], axis=1) In [51]: s Out[51]: 0 a 1 b 2 c 3 d dtype: object In [52]: d Out[52]: 0 1 0 a a 1 b b 2 NaN c 3 d d In [53]: s.str.cat(d, na_rep='-') Out[53]: 0 aaa 1 bbb 2 c-c 3 ddd dtype: object # 对齐拼接序列与带索引的对象成为新的序列

v0.23.0.新加入

对于拼接序列或者数据表,我们可以使用 join关键字来对齐索引。

In [54]: u = pd.Series(['b', 'd', 'a', 'c'], index=[1, 3, 0, 2]) In [55]: s Out[55]: 0 a 1 b 2 c 3 d dtype: object In [56]: u Out[56]: 1 b 3 d 0 a 2 c dtype: object In [57]: s.str.cat(u) Out[57]: 0 ab 1 bd 2 ca 3 dc dtype: object In [58]: s.str.cat(u, join='left') Out[58]: 0 aa 1 bb 2 cc 3 dd dtype: object

警告

如果不使用join 关键字, cat()open in new window 方法将会滚回到0.23.0版之前,即(无对齐)模式。但如果任何的索引不一致时,将会抛出一个 FutureWarning 警告,因为在未来的版本中,默认行为将改为join='left' 。

join 的选项为('left', 'outer', 'inner', 'right')中的一个。 特别的,对齐操作使得两个对象可以是不同的长度。

In [59]: v = pd.Series(['z', 'a', 'b', 'd', 'e'], index=[-1, 0, 1, 3, 4]) In [60]: s Out[60]: 0 a 1 b 2 c 3 d dtype: object In [61]: v Out[61]: -1 z 0 a 1 b 3 d 4 e dtype: object In [62]: s.str.cat(v, join='left', na_rep='-') Out[62]: 0 aa 1 bb 2 c- 3 dd dtype: object In [63]: s.str.cat(v, join='outer', na_rep='-') Out[63]: -1 -z 0 aa 1 bb 2 c- 3 dd 4 -e dtype: object

当others是一个数据表时,也可以执行相同的对齐操作:

In [64]: f = d.loc[[3, 2, 1, 0], :] In [65]: s Out[65]: 0 a 1 b 2 c 3 d dtype: object In [66]: f Out[66]: 0 1 3 d d 2 NaN c 1 b b 0 a a In [67]: s.str.cat(f, join='left', na_rep='-') Out[67]: 0 aaa 1 bbb 2 c-c 3 ddd dtype: object # 将一个序列与多个对象拼接为一个新的序列

所有的一维,类列表对象都可以任意组合进一个类列表的容器(包括迭代器,dict-视图等):

In [68]: s Out[68]: 0 a 1 b 2 c 3 d dtype: object In [69]: u Out[69]: 1 b 3 d 0 a 2 c dtype: object In [70]: s.str.cat([u, u.to_numpy()], join='left') Out[70]: 0 aab 1 bbd 2 cca 3 ddc dtype: object

除了那些有索引的,所有传入没有索引的元素(如np.ndarray)必须与序列或索引有相同的长度。但是,只要禁用对齐join=None,那么序列或索引就可以是任意长度。

In [71]: v Out[71]: -1 z 0 a 1 b 3 d 4 e dtype: object In [72]: s.str.cat([v, u, u.to_numpy()], join='outer', na_rep='-') Out[72]: -1 -z-- 0 aaab 1 bbbd 2 c-ca 3 dddc 4 -e-- dtype: object

如果在一个包含不同的索引的others列表上使用join='right',所有索引的并集将会被作为最终拼接的基础:

In [73]: u.loc[[3]] Out[73]: 3 d dtype: object In [74]: v.loc[[-1, 0]] Out[74]: -1 z 0 a dtype: object In [75]: s.str.cat([u.loc[[3]], v.loc[[-1, 0]]], join='right', na_rep='-') Out[75]: -1 --z 0 a-a 3 dd- dtype: object # 使用.str进行索引

你可以使用 []方法来直接索引定位。如果你的索引超过了字符串的结尾,将返回NaN。

In [76]: s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, ....: 'CABA', 'dog', 'cat']) ....: In [77]: s.str[0] Out[77]: 0 A 1 B 2 C 3 A 4 B 5 NaN 6 C 7 d 8 c dtype: object In [78]: s.str[1] Out[78]: 0 NaN 1 NaN 2 NaN 3 a 4 a 5 NaN 6 A 7 o 8 a dtype: object # 提取子字符串# 提取第一个匹配的对象 (extract)

警告

在 0.18.0中,extract拥有了 expand 参数。当 expand=False时, 将返回一个序列,索引或者数据表, 这取决于原对象和正则表达式(之前的版本也是如此)。当 expand=True时,它则总是返回一个DataFrame,这样可以更加一致,并且减少用户的混淆。 Expand=True 从0.23.0版本之后成为默认值。

extract 方法接受一个至少含有一个捕获组的 正则表达式open in new window。

使用超过一个捕获组的正则表达式则会提取并返回一个数据表,每一列为一个捕获组。

In [79]: pd.Series(['a1', 'b2', 'c3']).str.extract(r'([ab])(\d)', expand=False) Out[79]: 0 1 0 a 1 1 b 2 2 NaN NaN

没有成功匹配的元素将会返回一行NaN。因此,一个序列的混乱的字符串可以被‘转换’为一个类似索引的序列或数据表。返回的内容会更为清爽,而且不需要使用get()方法来访问元组中的成员或者re.match对象。返回的类型将总是object类,即使匹配失败,返回的全是NaN。

有名称的捕获组,如:

In [80]: pd.Series(['a1', 'b2', 'c3']).str.extract(r'(?P[ab])(?P\d)', ....: expand=False) ....: Out[80]: letter digit 0 a 1 1 b 2 2 NaN NaN

可选组类似,如:

In [81]: pd.Series(['a1', 'b2', '3']).str.extract(r'([ab])?(\d)', expand=False) Out[81]: 0 1 0 a 1 1 b 2 2 NaN 3

也可以被使用。注意,任何有名称的捕获组,其名称都会被用做列名,否则将会直接使用数字。

如果仅使用正则表达式捕获一个组,而expand=True,那么仍然将返回一个数据表。

In [82]: pd.Series(['a1', 'b2', 'c3']).str.extract(r'[ab](\d)', expand=True) Out[82]: 0 0 1 1 2 2 NaN

如果expand=False,则会返回一个序列。

In [83]: pd.Series(['a1', 'b2', 'c3']).str.extract(r'[ab](\d)', expand=False) Out[83]: 0 1 1 2 2 NaN dtype: object

在索引上使用正则表达式,并且仅捕获一个组时,将会返回一个数据表,如果expand=True。

In [84]: s = pd.Series(["a1", "b2", "c3"], ["A11", "B22", "C33"]) In [85]: s Out[85]: A11 a1 B22 b2 C33 c3 dtype: object In [86]: s.index.str.extract("(?P[a-zA-Z])", expand=True) Out[86]: letter 0 A 1 B 2 C

如果expand=False,则返回一个Index。

In [87]: s.index.str.extract("(?P[a-zA-Z])", expand=False) Out[87]: Index(['A', 'B', 'C'], dtype='object', name='letter')

如果在索引上使用正则并捕获多个组,则返回一个数据表,如果expand=True。

In [88]: s.index.str.extract("(?P[a-zA-Z])([0-9]+)", expand=True) Out[88]: letter 1 0 A 11 1 B 22 2 C 33

如果 expand=False,则抛出ValueError。

>>> s.index.str.extract("(?P[a-zA-Z])([0-9]+)", expand=False) ValueError: only one regex group is supported with Index

下面的表格总结了extract (expand=False)时的行为(输入对象在第一列,捕获组的数量在第一行)

| 1 group | >1 group ---|---|--- Index | Index | ValueError Series | Series | DataFrame

# 提取所有的匹配 (extractall)

v0.18.0. 新加入

不同于 extract(只返回第一个匹配),

In [89]: s = pd.Series(["a1a2", "b1", "c1"], index=["A", "B", "C"]) In [90]: s Out[90]: A a1a2 B b1 C c1 dtype: object In [91]: two_groups = '(?P[a-z])(?P[0-9])' In [92]: s.str.extract(two_groups, expand=True) Out[92]: letter digit A a 1 B b 1 C c 1

extractall方法返回所有的匹配。extractall总是返回一个带有行多重索引的数据表,最后一级多重索引被命名为match,它指出匹配的顺序

In [93]: s.str.extractall(two_groups) Out[93]: letter digit match A 0 a 1 1 a 2 B 0 b 1 C 0 c 1

当所有的对象字串都只有一个匹配时,

In [94]: s = pd.Series(['a3', 'b3', 'c2']) In [95]: s Out[95]: 0 a3 1 b3 2 c2 dtype: object

extractall(pat).xs(0, level='match') 的返回与extract(pat)相同。

In [96]: extract_result = s.str.extract(two_groups, expand=True) In [97]: extract_result Out[97]: letter digit 0 a 3 1 b 3 2 c 2 In [98]: extractall_result = s.str.extractall(two_groups) In [99]: extractall_result Out[99]: letter digit match 0 0 a 3 1 0 b 3 2 0 c 2 In [100]: extractall_result.xs(0, level="match") Out[100]: letter digit 0 a 3 1 b 3 2 c 2

索引也支持.str.extractall。 它返回一个数据表,其中包含与Series.str.estractall相同的结果,使用默认索引(从0开始)

v0.19.0. 新加入

In [101]: pd.Index(["a1a2", "b1", "c1"]).str.extractall(two_groups) Out[101]: letter digit match 0 0 a 1 1 a 2 1 0 b 1 2 0 c 1 In [102]: pd.Series(["a1a2", "b1", "c1"]).str.extractall(two_groups) Out[102]: letter digit match 0 0 a 1 1 a 2 1 0 b 1 2 0 c 1 # 测试匹配或包含模式的字符串

你可以检查是否一个元素包含一个可以匹配到的正则表达式:

In [103]: pattern = r'[0-9][a-z]' In [104]: pd.Series(['1', '2', '3a', '3b', '03c']).str.contains(pattern) Out[104]: 0 False 1 False 2 True 3 True 4 True dtype: bool

或者是否元素完整匹配一个正则表达式

In [105]: pd.Series(['1', '2', '3a', '3b', '03c']).str.match(pattern) Out[105]: 0 False 1 False 2 True 3 True 4 False dtype: bool

match和contains的区别是是否严格匹配。match严格基于re.match,而contains基于re.search。

类似match, contains, startswith 和 endswith 可以传入一个额外的na参数,因此,因此缺失值在匹配时可以被认为是True或者False:

In [106]: s4 = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat']) In [107]: s4.str.contains('A', na=False) Out[107]: 0 True 1 False 2 False 3 True 4 False 5 False 6 True 7 False 8 False dtype: bool # 建立一个指示变量

你从字符串列可以抽出一个哑变量。例如,是否他们由|分割:

In [108]: s = pd.Series(['a', 'a|b', np.nan, 'a|c']) In [109]: s.str.get_dummies(sep='|') Out[109]: a b c 0 1 0 0 1 1 1 0 2 0 0 0 3 1 0 1

索引也支持get_dummies,它返回一个多重索引:

v0.18.1. 新加入

In [110]: idx = pd.Index(['a', 'a|b', np.nan, 'a|c']) In [111]: idx.str.get_dummies(sep='|') Out[111]: MultiIndex([(1, 0, 0), (1, 1, 0), (0, 0, 0), (1, 0, 1)], names=['a', 'b', 'c'])

参见 get_dummies()open in new window.

# 方法总览方法描述cat()open in new window拼接字符串split()open in new window基于分隔符切分字符串rsplit()open in new window基于分隔符,逆向切分字符串get()open in new window索引每一个元素(返回第i个元素)join()open in new window使用传入的分隔符依次拼接每一个元素get_dummies()open in new window用分隔符切分字符串,并返回一个含有哑变量的数据表contains()open in new window返回一个布尔矩阵表明是每个元素包含字符串或正则表达式replace()open in new window将匹配到的子串或正则表达式替换为另外的字符串,或者一个可调用对象的返回值repeat()open in new window值复制(s.str.repeat(3)等价于x * 3)pad()open in new window将白空格插入到字符串的左、右或者两端center()open in new window等价于str.centerljust()open in new window等价于str.ljustrjust()open in new window等价于str.rjustzfill()open in new window等价于str.zfillwrap()open in new window将长字符串转换为不长于给定长度的行slice()open in new window将序列中的每一个字符串切片slice_replace()open in new window用传入的值替换每一个字串中的切片count()open in new window对出现符合的规则进行计数startswith()open in new window等价于str.startswith(pat)endswith()open in new window等价于 str.endswith(pat)findall()open in new window返回每一个字串中出现的所有满足样式或正则的匹配match()open in new window素调用 re.match,并以列表形式返回匹配到的组extract()open in new windowCall 对每一个元素调用 re.search, 并以数据表的形式返回。行对应原有的一个元素,列对应所有捕获的组extractall()open in new window一个元素调用 re.findall, 并以数据表的形式返回。行对应原有的一个元素,列对应所有捕获的组len()open in new window计算字符串长度strip()open in new window等价于str.striprstrip()open in new window等价于str.rstriplstrip()open in new window等价于str.lstrippartition()open in new window等价于 str.partitionrpartition()open in new window等价于 str.rpartitionlower()open in new window等价于 str.lowercasefold()open in new window等价于 str.casefoldupper()open in new window等价于 str.upperfind()open in new window等价于str.findrfind()open in new window等价于 str.rfindindex()open in new window等价于 str.indexrindex()open in new window等价于 str.rindexcapitalize()open in new window等价于 str.capitalizeswapcase()open in new window等价于 str.swapcasenormalize()open in new window返回Unicode 标注格式。等价于 unicodedata.normalizetranslate()open in new window等价于 str.translateisalnum()open in new window等价于 str.isalnumisalpha()open in new window等价于 str.isalphaisdigit()open in new window等价于 str.isdigitisspace()open in new window等价于 str.isspaceislower()open in new window等价于 str.islowerisupper()open in new window等价于 str.isupperistitle()open in new window等价于 str.istitleisnumeric()open in new window等价于 str.isnumericisdecimal()open in new window等价于 str.isdecimal


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有